frontend/pages/e/[uuid]/details.tsx (view raw)
1import moment from 'moment';
2import Tooltip from '@mui/material/Tooltip';
3import IconButton from '@mui/material/IconButton';
4import Button from '@mui/material/Button';
5import Box from '@mui/material/Box';
6import Link from '@mui/material/Link';
7import Card from '@mui/material/Card';
8import Container from '@mui/material/Container';
9import TextField from '@mui/material/TextField';
10import Typography from '@mui/material/Typography';
11import PlaceOutlinedIcon from '@mui/icons-material/PlaceOutlined';
12import EventIcon from '@mui/icons-material/Event';
13import TuneIcon from '@mui/icons-material/Tune';
14import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
15import {useTheme} from '@mui/material/styles';
16import {DatePicker} from '@mui/x-date-pickers/DatePicker';
17import {PropsWithChildren, useState} from 'react';
18import {useTranslation} from 'react-i18next';
19import pageUtils from '../../../lib/pageUtils';
20import ShareEvent from '../../../containers/ShareEvent';
21import PlaceInput from '../../../containers/PlaceInput';
22import usePermissions from '../../../hooks/usePermissions';
23import useEventStore from '../../../stores/useEventStore';
24import useToastStore from '../../../stores/useToastStore';
25import EventLayout, {TabComponent} from '../../../layouts/Event';
26import {
27 EventByUuidDocument,
28 useUpdateEventMutation,
29} from '../../../generated/graphql';
30
31interface Props {
32 eventUUID: string;
33 announcement?: string;
34}
35
36const Page = (props: PropsWithChildren<Props>) => {
37 return <EventLayout {...props} Tab={DetailsTab} />;
38};
39
40const DetailsTab: TabComponent<Props> = ({}) => {
41 const {t} = useTranslation();
42 const {
43 userPermissions: {canEditEventDetails},
44 } = usePermissions();
45 const theme = useTheme();
46 const [updateEvent] = useUpdateEventMutation();
47 const addToast = useToastStore(s => s.addToast);
48 const setEventUpdate = useEventStore(s => s.setEventUpdate);
49 const event = useEventStore(s => s.event);
50 const [isEditing, setIsEditing] = useState(false);
51
52 if (!event) return null;
53
54 const onSave = async e => {
55 try {
56 const {uuid, ...data} = event;
57 const {id, travels, waitingPassengers, __typename, ...input} = data;
58 await updateEvent({
59 variables: {
60 uuid,
61 eventUpdate: {
62 ...input,
63 },
64 },
65 refetchQueries: ['eventByUUID'],
66 });
67 setIsEditing(false);
68 } catch (error) {
69 console.error(error);
70 addToast(t('event.errors.cant_update'));
71 }
72 };
73
74 const modifyButton = isEditing ? (
75 <Tooltip
76 title={t('event.details.save')}
77 sx={{
78 position: 'absolute',
79 top: theme.spacing(2),
80 right: theme.spacing(2),
81 }}
82 >
83 <IconButton color="primary" onClick={onSave}>
84 <CheckCircleOutlineIcon />
85 </IconButton>
86 </Tooltip>
87 ) : (
88 <Tooltip
89 title={t('event.details.modify')}
90 sx={{
91 position: 'absolute',
92 top: theme.spacing(2),
93 right: theme.spacing(2),
94 }}
95 >
96 <IconButton color="primary" onClick={() => setIsEditing(true)}>
97 <TuneIcon />
98 </IconButton>
99 </Tooltip>
100 );
101
102 return (
103 <Box
104 sx={{
105 position: 'relative',
106 }}
107 >
108 <Container
109 sx={{
110 p: 4,
111 mt: 6,
112 mb: 11,
113 mx: 0,
114 [theme.breakpoints.down('md')]: {
115 p: 2,
116 },
117 }}
118 >
119 <Card
120 sx={{
121 position: 'relative',
122 maxWidth: '100%',
123 width: '350px',
124 p: 2,
125 }}
126 >
127 <Typography variant="h4" pb={2}>
128 {t('event.details')}
129 </Typography>
130 {canEditEventDetails() && modifyButton}
131 <Box pt={2} pr={1.5}>
132 <Typography variant="overline">{t('event.fields.name')}</Typography>
133 <Typography variant="body1">
134 {isEditing ? (
135 <TextField
136 size="small"
137 fullWidth
138 value={event.name}
139 onChange={e => setEventUpdate({name: e.target.value})}
140 name="name"
141 id="EditEventName"
142 />
143 ) : (
144 <Typography variant="body1" id="EventName">
145 {event.name ?? t('event.fields.empty')}
146 </Typography>
147 )}
148 </Typography>
149 </Box>
150 <Box pt={2} pr={1.5}>
151 <Typography variant="overline">{t('event.fields.date')}</Typography>
152 {isEditing ? (
153 <Typography variant="body1">
154 <DatePicker
155 slotProps={{
156 textField: {
157 size: 'small',
158 id: `EditEventDate`,
159 fullWidth: true,
160 placeholder: t('event.fields.date_placeholder'),
161 },
162 }}
163 format="DD/MM/YYYY"
164 value={moment(event.date)}
165 onChange={date =>
166 setEventUpdate({
167 date: !date ? null : moment(date).format('YYYY-MM-DD'),
168 })
169 }
170 />
171 </Typography>
172 ) : (
173 <Box position="relative">
174 <Typography variant="body1" id="EventDate">
175 {event.date
176 ? moment(event.date).format('DD/MM/YYYY')
177 : t('event.fields.empty')}
178 </Typography>
179 <EventIcon
180 color="action"
181 sx={{
182 position: 'absolute',
183 right: theme.spacing(-0.5),
184 top: 0,
185 }}
186 />
187 </Box>
188 )}
189 </Box>
190 <Box pt={2} pr={1.5}>
191 <Typography variant="overline">
192 {t('event.fields.address')}
193 </Typography>
194 {isEditing ? (
195 <PlaceInput
196 place={event.address}
197 latitude={event.latitude}
198 longitude={event.longitude}
199 onSelect={({place, latitude, longitude}) =>
200 setEventUpdate({
201 address: place,
202 latitude,
203 longitude,
204 })
205 }
206 />
207 ) : (
208 <Box position="relative">
209 <Typography variant="body1" id="EventAddress" sx={{pr: 3}}>
210 {event.address ? (
211 <Link
212 target="_blank"
213 rel="noreferrer"
214 href={`https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(
215 event.address
216 )}`}
217 onClick={e => e.preventDefault}
218 >
219 {event.address}
220 </Link>
221 ) : (
222 t('event.fields.empty')
223 )}
224 </Typography>
225 <PlaceOutlinedIcon
226 color="action"
227 sx={{
228 position: 'absolute',
229 right: theme.spacing(-0.5),
230 top: 0,
231 }}
232 />
233 </Box>
234 )}
235 </Box>
236 <Box pt={2} pr={1.5}>
237 <Typography variant="overline">
238 {t('event.fields.description')}
239 </Typography>
240 {isEditing ? (
241 <Typography variant="body1">
242 <TextField
243 fullWidth
244 multiline
245 maxRows={4}
246 inputProps={{maxLength: 250}}
247 value={event.description || ''}
248 onChange={e => setEventUpdate({description: e.target.value})}
249 id={`EditEventDescription`}
250 name="description"
251 />
252 </Typography>
253 ) : (
254 <Typography variant="body1" id="EventDescription" sx={{pr: 3}}>
255 {event.description ?? t('event.fields.empty')}
256 </Typography>
257 )}
258 </Box>
259 {!isEditing && (
260 <ShareEvent
261 title={`Caroster ${event.name}`}
262 sx={{width: '100%', mt: 2}}
263 />
264 )}
265 </Card>
266 </Container>
267 </Box>
268 );
269};
270
271export const getServerSideProps = pageUtils.getServerSideProps(
272 async (context, apolloClient) => {
273 const {uuid} = context.query;
274 const {host = ''} = context.req.headers;
275 let event = null;
276
277 // Fetch event
278 try {
279 const {data} = await apolloClient.query({
280 query: EventByUuidDocument,
281 variables: {uuid},
282 });
283 event = data?.eventByUUID?.data;
284 } catch (error) {
285 return {
286 notFound: true,
287 };
288 }
289
290 return {
291 props: {
292 eventUUID: uuid,
293 metas: {
294 title: event?.attributes?.name || '',
295 url: `https://${host}${context.resolvedUrl}`,
296 },
297 },
298 };
299 }
300);
301export default Page;